//=====================================================================
//
//  File:		ChangeCase.cs
//
//  Summary:	This file contains the implementation of the ChangeCase transformation component.
//				This sample demonstrates how to write a transformation component
//				with a synchronous output. It changes the case of a character in a string to 
//				lower case or upper case. Changes are made to the columns 
//				as they pass through the component, it does not add any output columns to the data flow.
//
//  Date:		6/15/2004
//
//---------------------------------------------------------------------
//
//  This file is part of the Microsoft SQL Server Code Samples.
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//	This source code is intended only as a supplement to Microsoft
//	Development Tools and/or on-line documentation.  See these other
//	materials for detailed information regarding Microsoft code samples.
//
//	THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//	PARTICULAR PURPOSE.
//
//===================================================================== 

using System;
using System.Collections;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using Microsoft.SqlServer.Dts.Runtime;

namespace Microsoft.Samples.SqlServer.Dts
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1405:ComVisibleTypeBaseTypesShouldBeComVisible"), ComVisible(true)]
    [
        DtsPipelineComponent
        (
            DisplayName = "ChangeCaseCS",
            Description = "Changes the case of a character in a string.",
            IconResource = "Microsoft.Samples.SqlServer.Dts.ChangeCase.ico"
        )]
    public class ChangeCase : PipelineComponent
    {
        #region Member data

        public enum Operation
        {
            ToUpper,
            ToLower
        }

        private struct ColumnInfo
        {
            public int bufferColumnIndex;
            public int characterIndex;
            public Operation operation;
            public DTSRowDisposition columnDisposition;
            public int lineageID;
        }

        /// <summary>
        /// Used when directing rows to the error output. The integer specifies the error code for the column.
        /// The string specifies the message displayed for the error code.
        /// </summary>
        const int INVALIDCHARACTERINDEX = 0x1;
        const string INVALIDCHARACTERINDEXMESSAGE = "The character index to change is outside the length of the column value.";


        private bool Cancel;
        private ColumnInfo[] columnInfos;

        #endregion

        #region Design Time

        #region ProvideComponentProperties
        /// <summary>
        /// Called when the component is initially added to the data flow task. Add the input, output, and error output.
        /// </summary>
        public override void ProvideComponentProperties()
        {
            ComponentMetaData.UsesDispositions = true;

            //	Add the input
            IDTSInput100 input = ComponentMetaData.InputCollection.New();
            input.Name = "ChangeCaseInput";
            input.ErrorRowDisposition = DTSRowDisposition.RD_FailComponent;

            //	Add the output
            IDTSOutput100 output = ComponentMetaData.OutputCollection.New();
            output.Name = "ChangeCaseOutput";
            output.SynchronousInputID = input.ID;
            output.ExclusionGroup = 1;

            //	Add the error output 
            AddErrorOutput("ChangeCaseErrorOutput", input.ID, output.ExclusionGroup);
        }
        #endregion

        #region Validate
        [CLSCompliant(false)]
        public override DTSValidationStatus Validate()
        {
            ///	If there is an input column that no longer exists in the Virtual input collection,
            /// return needs new meta data. The designer will then call ReinitalizeMetadata which will clean up the input collection.
            if (ComponentMetaData.AreInputColumnsValid == false)
                return DTSValidationStatus.VS_NEEDSNEWMETADATA;

            /// Check each Input column in the collection:
            /// UsageType == UT_READWRITE
            /// DataType == DT_WSTR or DT_TEXT, or DT_STR
            foreach (IDTSInputColumn100 column in ComponentMetaData.InputCollection[0].InputColumnCollection)
            {
                /// Validate column usage type.
                if (column.UsageType != DTSUsageType.UT_READWRITE)
                {
                    ComponentMetaData.FireError(0, column.IdentificationString, "Input columns must be set to READWRITE.", "", 0, out Cancel);
                    return DTSValidationStatus.VS_ISBROKEN;
                }

                /// Validate column datatype.
                if (column.DataType != DataType.DT_WSTR && column.DataType != DataType.DT_STR && column.DataType != DataType.DT_TEXT)
                {
                    ComponentMetaData.FireError(HResults.DTS_E_INVALIDDATATYPE, ComponentMetaData.Name, "The DataType " + column.DataType.ToString() + " of " + column.IdentificationString + " is not supported by the " + ComponentMetaData.Name + " component.", "", 0, out Cancel);
                    return DTSValidationStatus.VS_ISBROKEN;
                }

            }
            return base.Validate();
        }
        #endregion

        #region ReinitializeMetaData
        /// <summary>
        /// Called after the component has returned VS_NEEDSNEWMETADATA from Validate. Removes any input columns that 
        /// no longer exist in the Virtual Input Collection.
        /// </summary>
        public override void ReinitializeMetaData()
        {
            this.ComponentMetaData.RemoveInvalidInputColumns();
            base.ReinitializeMetaData();
        }
        #endregion

        #region SetUsageType
        /// <summary>
        /// Called when a user has selected an Input column for the component. This component only accepts input columns
        /// that have DTSUsageType.UT_READWRITE. Any other usage types are rejected.
        /// </summary>
        /// <param name="inputID">The ID of the input that the column is inserted in.</param>
        /// <param name="virtualInput">The virtual input object containing that contains the new column.</param>
        /// <param name="lineageID">The lineageID of the virtual input column.</param>
        /// <param name="usageType">The DTSUsageType parameter that specifies how the column is used by the component.</param>
        /// <returns>The newly created IDTSInputColumn100.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.Exception.#ctor(System.String)"), CLSCompliant(false)]
        public override IDTSInputColumn100 SetUsageType(int inputID, IDTSVirtualInput100 virtualInput, int lineageID, DTSUsageType usageType)
        {
            if (usageType == DTSUsageType.UT_READONLY)
                throw new Exception("The UsageType must be set to ReadWrite.");

            //	Get the column
            IDTSInputColumn100 col = base.SetUsageType(inputID, virtualInput, lineageID, usageType);

            /// If the the base component returned null, then the usagetype was UT_IGNORED, and the column 
            /// was removed from the input collection.
            if (col != null)
            {
                col.ErrorRowDisposition = DTSRowDisposition.RD_NotUsed;
                col.ErrorOrTruncationOperation = "Error, when CharacterIndex exceeds the length of the column.";

                //	Add the column Index Custom property
                IDTSCustomProperty100 prop = col.CustomPropertyCollection.New();
                prop.Name = "CharacterIndex";
                prop.Value = 0;

                //	Operation custom property
                IDTSCustomProperty100 op = col.CustomPropertyCollection.New();
                op.Name = "Operation";
                op.Value = Operation.ToUpper;
            }
            return col;
        }
        #endregion

        #region DeleteOutput
        /// <summary>
        /// Called when an IDTSOutput100 is deleted from the component. Disallow outputs to be deleted by throwing an exception.
        /// </summary>
        /// <param name="outputID">The ID of the output to delete.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.Exception.#ctor(System.String)")]
        public override void DeleteOutput(int outputID)
        {
            throw new Exception("Can't delete output " + outputID.ToString(System.Globalization.CultureInfo.InvariantCulture));
        }
        #endregion

        #region InsertOutput
        /// <summary>
        /// Called when an IDTSOutput100 is added to the component. Disallow new outputs by throwing an exception.
        /// </summary>
        /// <param name="insertPlacement">The location, relative to the output specified by outputID,to insert the new output.</param>
        /// <param name="outputID">The ID of the output that the new output is located next to.</param>
        /// <returns></returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.Exception.#ctor(System.String)"), CLSCompliant(false)]
        public override IDTSOutput100 InsertOutput(DTSInsertPlacement insertPlacement, int outputID)
        {
            throw new Exception("Can't add output to the component.");
        }
        #endregion

        #endregion

        #region Runtime

        #region PreExecute
        /// <summary>
        /// Called prior to ProcessInput, the buffer column index, index of the character to change, and the operation
        /// for each column in the input collection is read, and stored.
        /// </summary>
        public override void PreExecute()
        {
            IDTSInput100 input = ComponentMetaData.InputCollection[0];
            columnInfos = new ColumnInfo[input.InputColumnCollection.Count];

            for (int x = 0; x < input.InputColumnCollection.Count; x++)
            {
                IDTSInputColumn100 column = input.InputColumnCollection[x];
                columnInfos[x] = new ColumnInfo();
                columnInfos[x].bufferColumnIndex = BufferManager.FindColumnByLineageID(input.Buffer, column.LineageID);
                columnInfos[x].characterIndex = (int)column.CustomPropertyCollection["CharacterIndex"].Value;
                columnInfos[x].operation = (Operation)column.CustomPropertyCollection["Operation"].Value;
                columnInfos[x].columnDisposition = column.ErrorRowDisposition;
                columnInfos[x].lineageID = column.LineageID;
            }
        }
        #endregion

        #region ProcessInput
        /// <summary>
        /// Called when a PipelineBuffer is passed to the component.
        /// </summary>
        /// <param name="inputID">The ID of the Input that the buffer contains rows for.</param>
        /// <param name="buffer">The PipelineBuffer containing the columns defined in the IDTSInput100.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.Exception.#ctor(System.String)")]
        public override void ProcessInput(int inputID, PipelineBuffer buffer)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("buffer");
            }

            if (!buffer.EndOfRowset)
            {
                IDTSInput100 input = ComponentMetaData.InputCollection.GetObjectByID(inputID);

                int errorOutputID = -1;
                int errorOutputIndex = -1;
                int defaultOutputId = -1;

                GetErrorOutputInfo(ref errorOutputID, ref errorOutputIndex);

                if (errorOutputIndex == 0)
                    defaultOutputId = ComponentMetaData.OutputCollection[1].ID;
                else
                    defaultOutputId = ComponentMetaData.OutputCollection[0].ID;


                while (buffer.NextRow())
                {
                    /// If the columnInfos array has zero dimensions, then 
                    /// no input columns have been selected for the component. 
                    /// Direct the row to the default output.
                    if (columnInfos.Length == 0)
                        buffer.DirectRow(defaultOutputId);

                    bool isError = false;

                    /// Iterate the columns in the columnInfos array.
                    for (int x = 0; x < columnInfos.Length; x++)
                    {
                        ColumnInfo colInfo = columnInfos[x];

                        /// Is the column null?
                        if (!buffer.IsNull(colInfo.bufferColumnIndex))
                        {
                            /// No, process it.
                            string columnValue = buffer.GetString(colInfo.bufferColumnIndex);

                            ///	Is the index of the character to change greater than the length of the string?
                            if (columnValue.Length <= colInfo.characterIndex)
                            {

                                if (input.ErrorRowDisposition == DTSRowDisposition.RD_FailComponent)
                                {
                                    /// User has specified to fail the component when an error occurs.
                                    throw new Exception("Failing component because the CharacterIndex is greater than the length of the column.");

                                }
                                else if (input.ErrorRowDisposition == DTSRowDisposition.RD_RedirectRow)
                                {
                                    /// User has specified to direct errors to the error output so direct the row.
                                    buffer.DirectErrorRow(errorOutputID, ChangeCase.INVALIDCHARACTERINDEX, colInfo.lineageID);

                                    /// Set the error flag so the row is not sent to the default output
                                    isError = true;

                                    /// Break the loop.
                                    break;
                                }
                            }
                            else
                            {
                                if (colInfo.operation == Operation.ToUpper)
                                {
                                    columnValue = columnValue.Substring(0, colInfo.characterIndex) + columnValue.Substring(colInfo.characterIndex, 1).ToUpper(System.Globalization.CultureInfo.InvariantCulture) + columnValue.Substring(colInfo.characterIndex + 1);
                                }
                                else
                                {
                                    columnValue = columnValue.Substring(0, colInfo.characterIndex) + columnValue.Substring(colInfo.characterIndex, 1).ToLower(System.Globalization.CultureInfo.InvariantCulture) + columnValue.Substring(colInfo.characterIndex + 1);
                                }
                                buffer.SetString(colInfo.bufferColumnIndex, columnValue);
                            }
                        }
                    }
                    /// Finished processing each of the columns in this row.
                    /// If an error occurred and the error output is configured, then the row has already been directed to the error output, if configured.
                    /// If not, then direct the row to the default output.
                    if (!isError)
                        buffer.DirectRow(defaultOutputId);
                }
            }
        }
        #endregion

        /// <summary>
        /// Called by the data flow to retrieve description of the error code provided to DirectErrorRow during ProcessInput.
        /// </summary>
        /// <param name="iErrorCode">The error code to retrieve the message for.</param>
        /// <returns>A string containing the error description.</returns>
        public override string DescribeRedirectedErrorCode(int iErrorCode)
        {
            if (iErrorCode == INVALIDCHARACTERINDEX)
                return INVALIDCHARACTERINDEXMESSAGE;
            return "";
        }


        #endregion
    }
}
